//------------------------------------------------------------------------------
// Includes files
//------------------------------------------------------------------------------
#include <GL/glut.h>
#include "mat44.hpp"
#include "Camera.hpp"
#include "DiffuseShader.hpp"
#include "ReflectShader.hpp"
#include "PointLight.hpp"
#include "SurfaceLight.hpp"
#include "Sphere.hpp"
#include "Mesh.hpp"
#include "MeshTool.hpp"
#include "BasicScene.hpp"
#include "Settings.hpp"
#include "RayTracingRenderer.hpp"
#include "GraphicDebugger.hpp"
#include "Tools/LoggerConsole.hpp"
#include "AABB.hpp"

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------
// -> D�finie dans le fichier de settings (Settings.hpp)

//------------------------------------------------------------------------------
// Forward declaration
//------------------------------------------------------------------------------
void sceneInitilization(void);
void display(void);
void reshape(int width, int height);
void keyboard(unsigned char key, int x, int y);

//------------------------------------------------------------------------------
// Variable
//------------------------------------------------------------------------------
// Surface de rendu
RenderingSurface surface(DEFAULT_WIDTH,DEFAULT_HEIGHT);

// Camera pour le debuggage
Camera debugCamera;

// Indique si l'on se situe en mode debug ou non (touche 'v' pour swapper)
bool debugMode = false;

// Information sur la zone de visualisation
int viewportX		= 0;
int viewportY		= 0;
int viewportWidth	= DEFAULT_WIDTH;
int viewportHeight	= DEFAULT_HEIGHT;

//------------------------------------------------------------------------------
// Main
//----------------------------------------------------------------------------
int main(int argc, char** argv)
{
	// Creation du logger
	Tools::ILogger::SetLogger(new Tools::LoggerConsole());

	//test intersection para
	VEC minPoint, maxPoint;
	vec4_init(minPoint, 0.f, 0.f, 0.f, 1.f);
	vec4_init(maxPoint, 1.f, 1.f, 1.f, 1.f);
	AABB para (minPoint, maxPoint);

	//lancer du rayon
	Ray myRay;
	vec4_init( myRay.Position, 0.5, 0.5, 2, 1 );
	vec4_init( myRay.Direction, 0, 0, -1, 0 );

	Ray::Intersection near;
	Ray::Intersection far;
	bool res = para.Intersect( myRay, near, far );
	
	vec3_printf( near.WorldPoint );
	vec3_printf( far.WorldPoint );
	printf("intersection: %d\n", res);

    	// Initialistaion de la scene
	//sceneInitilization();

    	// Initialisation de GLUT
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
	glutInitWindowSize(DEFAULT_WIDTH,DEFAULT_HEIGHT);
	glutCreateWindow("RayTracer");
	glutKeyboardFunc(&keyboard);
	glutDisplayFunc(&display);
	glutReshapeFunc(&reshape);
	glutMainLoop();

	GraphicDebugger::Destroy();

	return EXIT_SUCCESS;
}

//------------------------------------------------------------------------------
// Initialize scene
//----------------------------------------------------------------------------
void sceneInitilization()
{
    // Creation lumi�re 1 (ponctuelle)
	float lightPosition[] = {0,-3,3,1};
	PointLight light( lightPosition, 1.0f );



    // Creation lumi�re 2 (ponctuelle)
	float lightPosition2[] = {5,15,2,1};
	PointLight light2( lightPosition2, 1.0f );

    // Creation lumi�re 3 (surfacique)
	float lightPosition3[] = {5,15,2,1};
	float lightNormal3[] = {0,-1,0,0};
	SurfaceLight light3( lightPosition3, lightNormal3, 1.0f, 2 );

    // Creation des Shader (reflect et diffuse)
	DiffuseShader diffuseShader;
	ReflectShader reflectShader( 0.5f );

    // Creation de la sphere principale
	float sphereTransformation[16];
	float invertTransformation[16];
	mat44_identity( sphereTransformation );
	mat44_translate( sphereTransformation, 5,2.5,0 );
	mat44_inverse( invertTransformation, sphereTransformation );
    Sphere sphere( invertTransformation, &reflectShader, 2.0f );
    //Sphere sphere( invertTransformation, &diffuseShader, 2.0f );

    // Creation du cube
    float cubeTransformation[16];
	float cubeInvertTransformation[16];
	mat44_identity( cubeTransformation );
	mat44_translate( cubeTransformation, 9, 2.5,0 );
	mat44_inverse( cubeInvertTransformation, cubeTransformation );
	Mesh* cube;
	MeshTool::GetCube(cube, &diffuseShader, cubeInvertTransformation);

    // Creation de la sphere scondaire
	float sphere2Transformation[16];
	float invert2Transformation[16];
	mat44_identity( sphere2Transformation );
	mat44_translate( sphere2Transformation, 5,6.5,0 );
	mat44_inverse( invert2Transformation, sphere2Transformation );
	//Sphere sphere2( invert2Transformation, &diffuseShader, 1.0f );
	Sphere sphere2( invert2Transformation, &reflectShader, 1.0f );

    // Creation de la plane pour le sol
	float planeTransformation[16];
	mat44_identity( planeTransformation );
	Mesh* plane;
	MeshTool::GetPlane(plane, &diffuseShader, planeTransformation);

    // Creation de la camera de visualisation de la scene (materialise le point
    // d'observation  de la scene )
	Camera camera;
	vec4_init(camera.Position, 5, 3.5, 15, 1);

    // Creation et initilisation de la scene
	BasicScene* scene = new BasicScene();
	//scene->AddLight( &light );
	//scene->AddLight( &light2 );
	scene->AddLight( &light3 );
	scene->AddObject( &sphere );
	scene->AddObject( &sphere2 );
	scene->AddObject( plane );
	scene->AddObject( cube );

    // Creation du renderer et lancement du rendu
	RayTracingRenderer renderer( scene, &camera );
	renderer.Render( surface );
}
//------------------------------------------------------------------------------
// Keyboard
//----------------------------------------------------------------------------
void keyboard(unsigned char key, int x, int y)
{

	switch (key)
	{
		case 'z' :
			debugCamera.MoveDeeply( MOVING_INCREMENT );
			break;
		case 's' :
			debugCamera.MoveDeeply( -MOVING_INCREMENT);
			break;
		case 'q' :
			debugCamera.MoveHorizontaly( -MOVING_INCREMENT);
			break;
		case 'd' :
			debugCamera.MoveHorizontaly( MOVING_INCREMENT);
			break;
		case 'n' :
			debugCamera.MoveVertical( -MOVING_INCREMENT);
			break;
		case 'u' :
			debugCamera.MoveVertical( MOVING_INCREMENT);
			break;
		case 'j' :
			debugCamera.RotateCamera( 1.0f, 0.0f );
			break;
		case 'k' :
			debugCamera.RotateCamera( -1.0f,0.0f);
			break;
		case 'v' :
			debugMode = debugMode?false:true;
			break;

		case '\x1B':
			exit(EXIT_SUCCESS);
			break;
	}
}

//------------------------------------------------------------------------------
// Reshape
//----------------------------------------------------------------------------
void reshape(int width, int height)
{
	viewportX	= 0;
	viewportY	= 0;
	viewportWidth	= width;
	viewportHeight	= height;
}

//------------------------------------------------------------------------------
// Display rendered image
//----------------------------------------------------------------------------
void display()
{
    if( !debugMode )
    {
        // Initialisation d'OpenGL pour avoir une projection orthogonale
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClearDepth(1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Initialisation de la projection orthogonal pour afficher de simple
        // point (pixels
        glViewport(0, 0, viewportWidth, viewportHeight);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0.0, viewportWidth, 0.0, viewportHeight, -1.0, 1.0);

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        // Dessin de l'image
        surface.Render();

        glutSwapBuffers();
        glutPostRedisplay();
    }
    else
    {
        // Initialisation d'OpenGL pour avoir une projection orthogonale
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClearDepth(1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        // Initialisation de la projection perspective
        glViewport(0,0,viewportWidth,viewportHeight);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective( debugCamera.FieldOfView, float(viewportWidth)/float(viewportHeight), NEAR_VALUE, FAR_VALUE );
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        // Positionnement de la camera de debug
        gluLookAt( debugCamera.Position[0], debugCamera.Position[1], debugCamera.Position[2],
                   debugCamera.Position[0] + debugCamera.Direction[0], debugCamera.Position[1] + debugCamera.Direction[1], debugCamera.Position[2] + debugCamera.Direction[2],
                   debugCamera.Up[0], debugCamera.Up[1], debugCamera.Up[2] );

        // Dessin de l'image
        GraphicDebugger::Instance().Render();

        glutSwapBuffers();
        glutPostRedisplay();
    }
}
